#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h> // for ioctl()
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h> // for uint32_t
#include <dirent.h>
#include <linux/fb.h> // for LCD

int main()
{
    // // 以只读方式打开一个文件
    // int fd = open("a.txt", O_RDWR);

    // // 申请一块大小为1024字节的映射内存，并将之与文件fd相关联
    // // NULL: 由Linux负责分配映射内存，该参数必须是NULL
    // // 1024: 映射内存的大小
    // // PROT_READ|PROT_WRITE: 读写模式，必须是文件打开模式的子集
    // // MAP_SHARED: 对内存的操作立即反应到文件中
    // // fd: 内存映射的文件
    // // 0: 一般直接写0
    // char *p = mmap(NULL, 1024, PROT_READ|PROT_WRITE,
    //             MAP_SHARED, fd, 0);

    // // 将该映射内存的内容打印出来（即其相关联文件fd的内容）
    // printf("%s\n", p);

    // // 通过操作内存，间接修改了文件内容
    // p[0] = 'x';
    // printf("%s\n", p);
    
    // // 解除映射
    // munmap(p, 1024);

    // 1. tftp
    // 2. secureCRT + rx
    // 3. MobarXTerm + rz （自己配置、编译）
    // 4. U盘

    int lcd = open("/dev/fb0", O_RDWR);
    if(lcd == -1)
    {
        perror("打开LCD设备失败");
        exit(0);
    }

    struct fb_var_screeninfo vinfo;
    struct fb_fix_screeninfo finfo;
    // 获取LCD硬件参数
    ioctl(lcd, FBIOGET_VSCREENINFO, &vinfo);
    ioctl(lcd, FBIOGET_FSCREENINFO, &finfo);

    int width = vinfo.xres;    
    int height= vinfo.yres;    
    int bpp   = vinfo.bits_per_pixel;    
    int screensize = width * height * bpp/8;

    // 将映射内存设置为 800 * 960
    char *p = mmap(NULL, width * height*2 * bpp/8, PROT_READ|PROT_WRITE,
                   MAP_SHARED, lcd, 0);
    if(p == MAP_FAILED)
    {
        perror("内存映射失败");
        exit(0);
    }

    uint32_t colors[] = {0x00FF0000, 0x0000FF00, 0x00000FF};


    // 将可见区设置为A区
    vinfo.xoffset = 0;
    vinfo.yoffset = 0;
    ioctl(lcd, FBIOPAN_DISPLAY, &vinfo);

    for(int i=1; ; i++)
    {
        // 将tmp指向B区，在B区绘制图像
        char *tmp = p + screensize*(i%2);

        for(int h=0; h<height; h++)
        {
            for(int w=0; w<width; w++)
            {
                memcpy(tmp + w*4 + width*4*h, &colors[i%3], 4);
            }
        }

        // 将可见区设置为B区
        vinfo.xoffset = 0;
        vinfo.yoffset = 480*(i%2);
        ioctl(lcd, FBIOPAN_DISPLAY, &vinfo);

        sleep(1);
    }

    return 0;
}